#include <fstream>

#include <string.h>

#include <arpa/inet.h>

#include "common_defs.h"
#include "net_context.h"
#include "arp_impl.h"
#include "utils.h"

using namespace usnet;

std::unique_ptr<ARPImpl> ARPImpl::MakeUnique(const Ifconfig* ifcfg, const EndpointAddr* laddr)
{
    auto ctx = NetContext::GetContext();
    std::unique_ptr<ARPImpl> arp_impl { new(std::nothrow) ARPImpl { ifcfg, laddr } };
    if (arp_impl == nullptr) {
        LOG_ERROR(ctx, "failed to create ARPImpl instance");
        return nullptr;
    }
    if (arp_impl->InitARPCache() == false) {
        LOG_ERROR(ctx, "failed to init ARP address mapping cache");
        return nullptr;
    }
    if (arp_impl->InitEthImpl() ==  false) {
        LOG_ERROR(ctx, "failed to init EthImpl");
        return nullptr;
    }
    return arp_impl;
}

bool ARPImpl::InitARPCache()
{
    auto ctx = NetContext::GetContext();
    const char* arp_file { "/proc/net/arp" };
    std::ifstream inf { arp_file, std::ios::binary | std::ios::in };
    if (inf.is_open() == false) {
        LOG_ERROR(ctx, "failed to open file: %s.", arp_file);
        return false;
    }
    char line[128];
    char ip4addr[16];
    char ethaddr[32];
    char ifname[32];
    inf.getline(line, sizeof(line));
    while (inf.eof() == false) {
        inf.getline(line, sizeof(line));
        if (strlen(line) < 20 ) {
            continue;
        }
        if (sscanf(line, "%s %*s %*s %s %*s %s.", ip4addr, ethaddr, ifname) != 3) {
            LOG_ERROR(ctx, "failed to parse arp file line: %s.", line);
            return false;
        }
        if (ifcfg_->ifname_.compare(ifname) != 0) {
            continue;
        }
        if (arpcache4_tab_.count(ethaddr) == 0) {
            EthAddr2Num(arpcache4_tab_[ethaddr].eth_addr_, ethaddr);
        }
        auto& arp_cache = arpcache4_tab_[ethaddr];
        arp_cache.ipaddr_.push_back(0);
        if (inet_pton(AF_INET, ip4addr, &arp_cache.ipaddr_.back()) != 1) {
            LOG_ERROR(ctx, "failed to convert IPv4 address %s.", ip4addr);
            arp_cache.ipaddr_.pop_back();
            return false;
        }
    }
    return true;
}

bool ARPImpl::GetEthAddr(EndpointAddr* addr) const
{
    assert(laddr_->inet_proto_ == addr->inet_proto_);
    /**********************************************************
     * lookup ethaddr in local cache
    */
    if (laddr_->inet_proto_ == InetProto::IPv4) {
        for (auto iter = arpcache4_tab_.begin(); iter != arpcache4_tab_.end(); iter++) {
            for (const auto& target_addr : iter->second.ipaddr_) {
                if (target_addr == addr->be_ip_.v4_) {
                    memcpy(addr->eth_addr_, iter->second.eth_addr_, sizeof(addr->eth_addr_));
                    return true;
                }
            }
        }
    }
    if (laddr_->inet_proto_ == InetProto::IPv6) {
        for (auto iter = arpcache6_tab_.begin(); iter != arpcache6_tab_.end(); iter++) {
            for (const auto& target_addr : iter->second.ipaddr_) {
                if (ByteCompare(target_addr.data(), addr->be_ip_.v6_, 16) == 0) {
                    memcpy(addr->eth_addr_, iter->second.eth_addr_, sizeof(addr->eth_addr_));
                    return true;
                }
            }
        }
    }
    /**********************************************************/

    /**********************************************************
     * broadcast arp request 
    */
    // pass
    /**********************************************************/
    return false;
}

bool ARPImpl::InitEthImpl()
{
    auto ctx = NetContext::GetContext();
    eth_impl_ = EthImpl::MakeUnique(PPT::ARP, laddr_, nullptr, ifcfg_);
    if (eth_impl_ == nullptr) {
        LOG_ERROR(ctx, "failed to create EthImpl instance()");
        return false;
    }
    return true;
}